﻿using LeRenard.Core.Numbers;
using LeRenard.ScriptWars.Model.Scripting;
using System;
using System.Collections.Generic;

namespace LeRenard.ScriptWars.Model.Entities
{
    /// <summary>
    /// Implementation of <see cref="IEntity"/>.
    /// </summary>
    public class Entity : IEntity
    {
        #region Properties
        /// <summary>
        /// Holds track of the algorithm's next instruction.
        /// </summary>
        private IAlgorithmCycler _algorithmCycler;

        /// <summary>
        /// Unique identifier.
        /// </summary>
        public Guid Key { get { return _key; } }
        private readonly Guid _key;

        /// <summary>
        /// The entity's script.
        /// </summary>
        public IScript Script { get { return _script; } }
        private readonly IScript _script;

        /// <summary>
        /// Gets the direction the <see cref="IEntity"/> is currently facing.
        /// </summary>
        public Direction Direction { get { return _direction; } }
        private Direction _direction;

        /// <summary>
        /// Gets the previous operations that've been executed.
        /// </summary>
        public IEnumerable<IOperation> PreviousOperations { get { return _previousOperations; } }
        private IList<IOperation> _previousOperations;
        private bool _resetCalled;
        #endregion

        #region Construction
        /// <summary>
        /// Creates a new <see cref="Entity"/> object.
        /// </summary>
        /// <param name="script">The script the entity's based on.</param>
        public Entity(IScript script, Direction initialDirection = Direction.North)
        {
            ThrowIf.Null(() => script);

            var algorithmCycler = new AlgorithmCycler(script.Algorithm);

            _script = script;
            _algorithmCycler = algorithmCycler;
            _key = Guid.NewGuid();
            _direction = initialDirection;
            _previousOperations = new List<IOperation>();
            _resetCalled = false;
        }
        #endregion

        #region Methods
        /// <summary>
        /// Performs the next turn for this <see cref="IEntity"/>.
        /// </summary>
        /// <param name="game">The game this entity is part of.</param>
        public void NextTurn(IGame game)
        {
            var operation = _algorithmCycler.CurrentOperation;            
            operation.Execute(this, _algorithmCycler, game);

            if (_resetCalled) {
                _resetCalled = false;
            } else {
                if (_previousOperations.Count == 2) {
                    var lastPreviousOperation = _previousOperations[_previousOperations.Count - 1];
                    _previousOperations = new List<IOperation>();
                    _previousOperations.Add(operation);
                    _previousOperations.Add(lastPreviousOperation);
                } else {
                    _previousOperations.Add(operation);
                }
            }
        }

        /// <summary>
        /// Resets the previous operation stack.
        /// </summary>
        public void ResetPreviousOperations()
        {
            _previousOperations.Clear();
            _resetCalled = true;
        }

        /// <summary>
        /// Changes the entity's direction.
        /// </summary>        
        public void ChangeDirection(string direction)
        {
            ThrowIf.NullOrEmpty(() => direction);

            var directionModifier = 0;
            var newDirection = 0;
            var directionCycler = new Int32RangeCycler(new Int32Range(0, 3));
            directionCycler.Reset((int)Direction);

            if (direction.Equals("left")) {
                newDirection = directionCycler.Previous();
                newDirection = directionCycler.Previous();
            }
            if (direction.Equals("right")) {
                newDirection = directionCycler.Next();
                newDirection = directionCycler.Next();
            }

            _direction = (Direction)newDirection;
        }
        #endregion
    }
}
